package com.google.code.syncgear.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.QueryResultList;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.code.syncgear.core.Environment;
import com.google.code.syncgear.core.exception.FetchException;
import com.google.code.syncgear.model.Job;
import com.google.code.syncgear.plugin.AbstractFetch;
import com.google.code.syncgear.plugin.bean.FetchResult;
import com.google.code.syncgear.util.CacheUtil;
import com.google.code.syncgear.util.DateTimeUtil;
import com.google.code.syncgear.util.MessageUtil;

public final class Route {
	
	private static final Logger log = Logger.getLogger(Route.class.getName());
	
	private static int FETCH_LIMIT = 1;
	
	public static void execute() {
		log.info(MessageUtil.get("info_route_cursor_load"));
		String cursorName = Job.class.getName();
		Object cursorObject = CacheUtil.get(cursorName);

		log.info(MessageUtil.get("info_route_job_load"));
		log.info(MessageUtil.contact("FETCH_LIMIT", FETCH_LIMIT));
		Query query = new Query(Job.class.getSimpleName());
		query.addSort("createDate", SortDirection.ASCENDING);		
		PreparedQuery preparedQuery = Environment.getDatastoreService().prepare(query);
		FetchOptions fetchOptions = FetchOptions.Builder.withLimit(FETCH_LIMIT);
		if(cursorObject != null)
			fetchOptions.startCursor(Cursor.fromWebSafeString((String)cursorObject));
		
		QueryResultList<Entity> results = preparedQuery.asQueryResultList(fetchOptions);
		
		if(results.size() == 0) {
			log.info(MessageUtil.get("info_route_job_load_zero"));
			CacheUtil.put(cursorName, null);
		} else {
			log.info(MessageUtil.get("info_route_job_loaded"));
			log.info(MessageUtil.contact("SIZE", results.size()));
			CacheUtil.put(cursorName, results.getCursor().toWebSafeString());
			
			for (Entity result : results) {
				Job job = new Job();
				job.wrap(result);				
				try {
					log.info(
							MessageUtil.contact(
									MessageUtil.get("info_route_job_process"), job.getSource()
							)
				);
					process(job);
				} catch (InstantiationException e) {
					log.warning(e.getMessage());
					continue;
				} catch (IllegalAccessException e) {
					log.warning(e.getMessage());
					continue;
				} catch (ClassNotFoundException e) {
					log.warning(e.getMessage());
					continue;
				} catch (FetchException e) {
					log.warning(e.getMessage());
					continue;
				}
			}
		}
	}

	private static void process(Job job) throws InstantiationException, IllegalAccessException, ClassNotFoundException, FetchException {
		List<FetchResult> fetchResults = new ArrayList<FetchResult>();
		String cacheFetchName = "FETCH" + job.getFetch();
		Object cacheFetchObject = CacheUtil.get(cacheFetchName);
		AbstractFetch fetch = null;
		if(cacheFetchObject == null) {
			fetch = (AbstractFetch) Class.forName(job.getFetch()).newInstance();
			CacheUtil.put(cacheFetchName, fetch);
		} else {
			fetch = (AbstractFetch) cacheFetchObject;
		}		
		fetchResults = fetch.fire(job.getSource());
		Collections.sort(fetchResults);
		fetchResults = repeatFilter(fetchResults);
		if(fetchResults.size() > 0) {
			Dispatch dispatch = new Dispatch();
			dispatch.setJob(job);
			dispatch.setFetchResults(fetchResults);
			String key = job.getDispatch().concat(DateTimeUtil.getNow().toString());
			CacheUtil.put(key, dispatch);
			
			DatastoreService ds = Environment.getDatastoreService();					
			Queue queue = QueueFactory.getQueue("dispatch");
			try {
			    Transaction txn = ds.beginTransaction();
			    queue.add(TaskOptions.Builder.withUrl("/dispatch").method(TaskOptions.Method.GET).countdownMillis(1000 * 60).param("key", key));
			    txn.commit();		    
			} catch (DatastoreFailureException exception) {
				throw new FetchException(exception);
			}
		}
	}
	
	private static List<FetchResult> repeatFilter(List<FetchResult> fetchResults) {
		Map<String, Integer> map = new HashMap<String, Integer>();
		for(FetchResult fetchResult : fetchResults) {
			if(map.containsKey(fetchResult.getTitle())) {
				int value = map.get(fetchResult.getTitle()) + 1;
				map.put(fetchResult.getTitle(), value);
				fetchResult.setTitle(fetchResult.getTitle() + " (" + value + ")");
			} else {
				map.put(fetchResult.getTitle(), 0);
			}
		}
		return fetchResults;
	}

}